import dayjs from "dayjs";

// 使用类型谓词 is 收缩类型
function isNumber(val: unknown): val is number {
  return typeof val == "number" && isFinite(val);
}

function isBoolean(val: unknown): val is boolean {
  return typeof val == "boolean";
}

function isString(val: unknown): val is string {
  return typeof val == "string";
}

function isArray(val: unknown): val is Array<any> {
  return Array.isArray(val);
}

function isObject(val: unknown): val is object {
  return val != null && typeof val == "object" && !Array.isArray(val);
}
/**
 * isPlainObject 判断是否是朴素的对象
 *
 * @param val {any} 任意值
 * @returns {boolean} 是否是一个朴素的对象
 */
function isPlainObject(val: any): boolean {
  // 去除类型不是 object
  if (!val || toString.call(val) != "[object Object]") return false;
  const proto = val.__proto__;
  // 没有原型的对象是朴素对象。比如：Object.create(null)
  if (!proto) return true;
  // 必须是 {} 或者 new Object 生成的对象
  return val.constructor === Object && proto === Object.prototype;
}

function isEmpty(val: any): boolean {
  if (val == null) return true;
  if (isArray(val) || isString(val)) return !val.length;
  if (isPlainObject(val)) return !Object.keys(val).length;
  return false;
}

function isFunction(val: unknown): val is Function {
  return typeof val == "function";
}

function isNull(val: unknown): val is null {
  return val === null;
}

function isInteger(val: unknown): val is number {
  return Number.isInteger(val);
}

function isDate(val: unknown): val is Date {
  return toString.call(val) === "[object Date]";
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
function emptyFn() {}

type IsFn = (...val: any[]) => boolean;
/**
 * not 高阶函数：对参数执行结果取反
 *
 * @param {function} fn 任何返回布尔类型的函数
 * @returns {function}
 */
function not(fn: IsFn): IsFn {
  return (...args: any[]) => !fn(...args);
}

/**
 * 批量为 isXX 函数生成取反函数
 *
 * isNumber -> notNumber
 * isBoolean -> notBoolean
 * isString -> notString
 * isArray -> notArray
 * isObject -> notObject
 * isEmpty -> notEmpty
 * isFunction -> notFunction
 */
const {
    notNumber,
    notBoolean,
    notString,
    notArray,
    notObject,
    notEmpty,
    notFunction,
    notNull,
    notPlainObject,
  }: Record<string, IsFn> = Object.entries({
    isNumber,
    isBoolean,
    isString,
    isArray,
    isObject,
    isEmpty,
    isFunction,
    isNull,
    isPlainObject,
  }).reduce(
    (acc, [k, v]) => Object.assign(acc, { [k.replace("is", "not")]: not(v) }),
    {}
  ),
  stringify = (obj: Record<string, any>): string =>
    Object.entries(obj)
      .map(
        ([key, value]) =>
          `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
      )
      .join("&"),
  toArray = (v: any): Array<any> => {
    if (isObject(v)) return Object.values(v);
    return Array.from(v);
  },
  toBoolean = (val: any): boolean =>
    notString(val) ? Boolean(val) : val.toLowerCase() == "true",
  toDate = (val: any, fmt = "YYYY-MM-DD"): Date => dayjs(val, fmt).toDate(),
  toNumber = (val: any): number => {
    if (typeof val == "number") return val;
    return Number(val);
  };

const randId = () => Math.random().toString(36).slice(2);

function dataURLtoBlob(dataURL: string): Blob {
  const arr = dataURL.split(",");
  const type = arr[0].match(/:(.*?);/)![1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type });
}

function download(blob: Blob, fileName: string) {
  // 将 blob 对象转换为一个 url 对象
  const url = URL.createObjectURL(blob),
    link = document.createElement("a");
  Object.assign(link, {
    href: url,
    download: fileName,
    rel: "noopener",
    target: "_blank",
  });
  link.click();
}

export {
  emptyFn,
  not,
  stringify,
  toArray,
  toBoolean,
  toDate,
  toNumber,
  isInteger,
  isNumber,
  isBoolean,
  isString,
  isArray,
  isObject,
  isEmpty,
  isFunction,
  isNull,
  isPlainObject,
  isDate,
  notNumber,
  notBoolean,
  notString,
  notArray,
  notObject,
  notEmpty,
  notFunction,
  notNull,
  notPlainObject,
  randId,
  dataURLtoBlob,
  download,
};
